package com.fi.recogn.dto.table;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.comparator.CompareUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import com.fi.recogn.dto.ExcelMergeManagerGroupDTO;
import com.fi.recogn.enums.ExcelOrientationEnum;
import com.fi.recogn.util.MD5Utils;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Builder;
import lombok.Data;
import lombok.experimental.Accessors;
import org.jetbrains.annotations.Nullable;

import java.io.Serializable;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
 * @author songqiang
 * @desc
 * @created on 2023 年 11 月 11
 * @Date 10:19
 * @since 1.0
 */

@Data
@Builder
@ApiModel
@Accessors(chain = true)
public class Table implements Serializable {


    /**
     * 以长度为8的整形数组表示四边形， 语义左上角为起始点顺时针构成闭合区域。
     */
    @JsonIgnore
    private List<Long> position;

    /**
     * 表格类型:<p>
     * plain (文本区域而非表格区域): 仅返回文本行信息即字段"lines" <p>
     * table_with_line (有线表格): 仅返回单元格信息即字段"table_cells"<p>
     * table_without_line (无线表格): 仅返回单元格信息即字段"table_cells"
     */
    @ApiModelProperty("表格类型 plain (文本区域而非表格区域): 仅返回文本行信息即字段:lines \n" +
            "table_with_line (有线表格): 仅返回单元格信息即字段\"table_cells\"\n" +
            "table_without_line (无线表格): 仅返回单元格信息即字段\"table_cells\"")
    private String type;

    /**
     * 单元格数组，当前区域类型为有线表格或者无线表格时返回本字段。
     */
    @ApiModelProperty("单元格数组")
    private List<Cell> tableCells = new LinkedList<>();

    /**
     * 需要重新处理的表id
     */
    @JsonIgnore
    private Long needReHandleExcelId;

    /**
     * 处理数据有表格，但是数据库没有记载，说明有误
     * false：没有问题，true：存在问题
     */
    @JsonIgnore
    private boolean cellError;

    /**
     * 根据页眉页脚结果猜测是否可能向上合并，true 可以，false 不可以
     */
    @JsonIgnore
    private boolean mergeUp;
    /**
     * 根据页眉页脚结果猜测是否可能向下合并，true 可以，false 不可以
     */
    @JsonIgnore
    private boolean mergeDown;

    /**
     * 以文本行为单位的识别结果
     */
    @ApiModelProperty("以文本行为单位的识别结果")
    private List<Line> lines;

    @ApiModelProperty("提取到的表明")
    private String excelName;

    @ApiModelProperty("表原始名称")
    private String excelOriginalName;

    @ApiModelProperty("是否开启规则分组：true：开启，false：未开启")
    private boolean openRegularGroup;

    @JsonSerialize(using = ToStringSerializer.class)
    @ApiModelProperty("规则分组id")
    private Long regularGroupId;

    @JsonSerialize(using = ToStringSerializer.class)
    @ApiModelProperty("默认分组id")
    private Long defaultRegularGroupId;

    @ApiModelProperty("规则分组名称")
    private String regularGroupName;

    @JsonSerialize(using = ToStringSerializer.class)
    @ApiModelProperty("规则命中情形")
    private Long regularSceneId;

    @ApiModelProperty("规则情形名称")
    private String regularSceneName;
    /**
     * 记录保存的顺序
     */
    @ApiModelProperty("记录保存的顺序")
    private float sortIndex;

    /**
     * 记录所在页
     */
    @ApiModelProperty("记录所在页")
    private Integer pageNumber;

    @ApiModelProperty("合并过来的那个表的分页信息")
    private Integer mergeLastPageNumber;

    /**
     * 合并到哪张表的对应的id
     */
    @JsonSerialize(using = ToStringSerializer.class)
    @ApiModelProperty("合并到哪张表的对应的id")
    private Long mergeId;

    /**
     * 从哪个表拆分而来
     */
    @JsonSerialize(using = ToStringSerializer.class)
    @ApiModelProperty("从哪个表拆分而来")
    private Long splitId;

    /**
     * 当前表格id
     */
    @JsonSerialize(using = ToStringSerializer.class)
    @ApiModelProperty("当前表格id")
    private Long excelId;

    /**
     * 公告id
     */
    @JsonSerialize(using = ToStringSerializer.class)
    @ApiModelProperty("公告id")
    private Long accessId;

    /**
     * 表格状态
     */
    @ApiModelProperty("表格状态 0：不采纳，1：未设置，2：已设置")
    private Integer status;

    @ApiModelProperty("ocr识别错误标记，0：无误，1：有误")
    private Boolean ocrError;

    @ApiModelProperty("表错误信息")
    private String errorMsg;

    /**
     * 表头单元格集合
     */
    @ApiModelProperty("表头单元格集合")
    private List<Cell> headers;

    /**
     * 表头md5签名
     */
    @ApiModelProperty("表头md5签名")
    private String headerMd5;

    @ApiModelProperty("所属机构")
    private String manager;

    @ApiModelProperty("分组是否增加机构维度，true：是的，false：否")
    private Boolean byManager = Boolean.TRUE;

    @ApiModelProperty("合并组织id")
    private Integer mergeManagerId;

    @JsonIgnore
    @ApiModelProperty("合并的机构")
    private Set<String> mergeManagers;

    @JsonIgnore
    @ApiModelProperty("是否有空机构")
    private boolean hasNullManager;

    /**
     * 表格方向 0：横向 1:纵向
     */
    @ApiModelProperty("表格方向 0：横向 1:纵向")
    private Integer orientation;

    @ApiModelProperty("相同表格数量")
    private Integer sameExcelCount = 0;

    @ApiModelProperty("相同表格组下不同机构统计")
    private List<ExcelMergeManagerGroupDTO> sameExcelMangerCountList = new ArrayList<>();

    @JsonIgnore
    private String updateUser;

    @ApiModelProperty("关联人")
    private String relationUser;

    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    @ApiModelProperty("关联时间")
    private Date relationTime;

    /**
     * 提取类型
     *
     * {@link com.fi.recogn.enums.ExcelFetchTypeEnum}
     */
    @JsonIgnore
    private Integer fetchType;

    /**
     * 命中后台表
     */
    @JsonIgnore
    private Map<Long, String> hitBackendName = new HashMap<>(4);


    /**
     * 获取表第一行格头
     */
    @JsonIgnore
    public List<Cell> getTableFirstRow() {
        List<Cell> cells = tableCells.stream().filter(cell -> cell.getStartRow() == 0).collect(Collectors.toList());
        if (CollUtil.isEmpty(cells) && CollUtil.isNotEmpty(tableCells)) {
            int min = CollUtil.min(tableCells.stream().map(Cell::getStartRow).collect(Collectors.toSet()));
            return tableCells.stream().filter(cell -> cell.getStartRow() == min).collect(Collectors.toList());
        }
        return cells;
    }

    /**
     * 获取表格第一列
     *
     * @return list 列的集合
     */
    @JsonIgnore
    public List<Cell> getTableFirstCol() {
        return getTableCol(0);
    }

    @JsonIgnore
    public List<Cell> getTableCol(int startCol) {
        return tableCells.stream().filter(cell -> cell.getStartCol() == startCol).collect(Collectors.toList());
    }

    /**
     * 获取表格行数
     */
    @JsonIgnore
    public int getRowCount() {
        return tableCells.stream().map(Cell::getStartRow).collect(Collectors.toSet()).size();
    }

    /**
     * 获取指定行
     */
    @JsonIgnore
    public List<Cell> getRow(int index) {
        return tableCells.stream().filter(cell -> cell.getStartRow() == index).collect(Collectors.toList());
    }

    /**
     * 获取总的列数
     *
     * @return int 列数
     */
    @JsonIgnore
    public int getColCount() {
        return tableCells.stream().map(Cell::getStartCol).collect(Collectors.toSet()).size();
    }

    /**
     * 获取列的集合 map集合
     *
     * @return map k->第几列，v-> 当前列的单元格
     */
    @JsonIgnore
    public Map<Integer, List<Cell>> getColMap() {
        Map<Integer, List<Cell>> map = new TreeMap<>();
        for (Cell cell : tableCells) {
            map.computeIfAbsent(cell.getStartCol(), k -> new LinkedList<>()).add(cell);
        }
        return map;
    }

    /**
     * 获取指定列
     *
     * @param index 列号
     * @return list 单元格
     */
    @JsonIgnore
    public List<Cell> getCol(int index) {
        return tableCells.stream().filter(cell -> cell.getStartCol() == index).collect(Collectors.toList());
    }

    /**
     * 移除某一行数据
     *
     * @param index 第几行
     */
    @JsonIgnore
    public List<Cell> removeRow(int index) {
        tableCells = tableCells.stream().filter(cell -> cell.getStartRow() != index).collect(Collectors.toList());
        return tableCells;
    }

    /**
     * 获取指定单元格的位置
     */
    @JsonIgnore
    public List<Long> getTableCellPosition(String cellValue) {
        return tableCells.stream().filter(cell -> cell.getText().equals(cellValue))
                .findFirst()
                .map(Cell::getLines)
                .map(lines -> lines.get(0))
                .map(Line::getPosition)
                .orElse(null);
    }

    /**
     * 通过位置查找文本
     */
    @JsonIgnore
    public List<String> findSameLineOfTextByPosition(List<Long> position) {
        return lines.stream().filter(line -> {
            List<Long> linePosition = line.getPosition();
            // 获取最后一个元素,也就是左下角的Y轴
            Long leftBottomY = linePosition.get(linePosition.size() - 1);
            Long leftBottomY2 = position.get(position.size() - 1);
            long max = Math.max(leftBottomY, leftBottomY2);
            long min = Math.min(leftBottomY, leftBottomY2);
            return max - min <= 10;
        }).map(Line::getText).collect(Collectors.toList());
    }

    @JsonIgnore
    public EndNumIndex getEndNumIndex() {
        EndNumIndex endNumIndex = new EndNumIndex();
        if (CollUtil.isEmpty(this.getTableCells())) {
            return endNumIndex;
        }
        for (Cell cell : this.getTableCells()) {
            if (cell.getEndRow() > endNumIndex.getEndRowNum()) {
                endNumIndex.setEndRowNum(cell.getEndRow());
            }
            if (cell.getEndCol() > endNumIndex.getEndColNum()) {
                endNumIndex.setEndColNum(cell.getEndCol());
            }
        }
        return endNumIndex;
    }

    @JsonIgnore
    public List<Cell> getLastRow() {
        EndNumIndex endNumIndex = getEndNumIndex();
        List<Cell> cellList = new LinkedList<>();
        for (Cell cell : getTableCells()) {
            if (ObjUtil.equals(cell.getEndRow(), endNumIndex.getEndRowNum())) {
                cellList.add(cell);
            }
        }
        return cellList;
    }

    /**
     * 生成当前表的表头MD5值
     *
     * @return string MD5值
     */
    public String generateHeaderMd5() {
        return generateHeaderMd5(this.orientation, this.headers);
    }


    public static String generateHeaderMd5(Integer orientation, List<Cell> headers) {
        return generateHeaderMd5(orientation, headers, false);
    }

    /**
     * 生成表头md5值
     *
     * @param orientation 表格方向
     * @param headers     表头
     * @param isComplex   是否是复杂表头的子表头
     * @return string MD5值
     */
    public static String generateHeaderMd5(Integer orientation, List<Cell> headers, boolean isComplex) {
        if (CollUtil.isEmpty(headers)) {
            return null;
        }
        if (ObjUtil.equals(orientation, ExcelOrientationEnum.HORIZONTAL.orientation())) {
            // 先对表格进行水平排序
            headers.sort((o1, o2) -> CompareUtil.compare(o1.getStartCol(), o2.getStartCol()));
        } else if (ObjUtil.equals(orientation, ExcelOrientationEnum.VERTICAL.orientation())) {
            // 进行纵向排序
            headers.sort((o1, o2) -> CompareUtil.compare(o1.getStartRow(), o2.getEndRow()));
        } else {
            // 复杂表必须要有排序
            boolean anyMatch = headers.stream().anyMatch(item -> ObjUtil.isNull(item.getGroupSorted()));
            if (anyMatch) {
                return null;
            }
            // 复杂表头计算
            // 筛选出子表的表头，用逗号隔开，并在结尾处增加类型区分，生成最终的表头，在每个分组中提取一个md5
            Map<Integer, Set<String>> collect = headers.stream().collect(Collectors.groupingBy(Cell::getGroupSorted,
                    Collectors.mapping(item-> StrUtil.format("{}:{}",item.getGroupSorted(),item.getSubHeaderMd5()), Collectors.toSet())));
            List<String> headerMd5s = new ArrayList<>();
            collect.values().forEach(headerMd5s::addAll);
            headerMd5s.sort((o1, o2) -> CompareUtil.compare(Convert.toInt(o1.split(":")), Convert.toInt(o2.split(":"))));
            if (CollUtil.isEmpty(headerMd5s)) {
                return null;
            }
            String h = String.join(",", headerMd5s) + "-" + ExcelOrientationEnum.COMPLEX.orientation();
            return MD5Utils.MD5(h);
        }
        return getMd5(headers, isComplex);
    }

    /**
     * 单元格前面单个格式：startRow-endRow:startCol-endCol[txt]
     */
    @JsonIgnore
    private static final String SIGNED_FORMAT = "%s-%s:%s-%s[%s]";

    /**
     * 表格定位格式：startRow-endRow:startCol-endCol
     */
    @JsonIgnore
    private static final String LOCATION_FORMAT = "%s-%s:%s-%s";

    @JsonIgnore
    @Nullable
    private static String getMd5(List<Cell> headers, boolean isComplex) {
        List<String> headerTxt = new LinkedList<>();
        // 签名规则：行开始-行结束:列开始-列结束[标题] 多个标题逗号（英文）隔开
        // 这样操作能确保相同表格一定是一样的，避免出现顺序或者合并单元格后的标题，但是这里不能避免表格数量识别错误的情况出现
        for (Cell header : headers) {
            String sb = String.format(SIGNED_FORMAT, header.getStartRow(), header.getEndRow(), header.getStartCol(), header.getEndCol(), replaceDate(header.getText()));
            headerTxt.add(isComplex? replaceDate(header.getText()) : sb);
        }
        return MD5Utils.MD5(String.join(",", headerTxt));
    }

    @JsonIgnore
    private static final String REGEX_DATE = "(20\\d{2})([年.\\-/]+|\\x20+)" + "(\\d{1,2})([月.\\-/]+|\\x20+)" + "(\\d{1,2})(日)?";
    @JsonIgnore
    private static Pattern DATE_REGEX = Pattern.compile(REGEX_DATE);
    @JsonIgnore
    private static final String REPLACE_DATE_STR = "XXXX年XX月XX日";
    @JsonIgnore
    private static final String REGEX_FEN_E_CODE = "^[A-Za-z]{1,2}类?份?额[\\(（]份额代码[\\(（\\[【]?[A-Za-z\\d]+[】\\]）\\)][\\)）]?$";
    @JsonIgnore
    private static Pattern FEN_E_CODE_REGEX = Pattern.compile(REGEX_FEN_E_CODE);
    /**
     * 替换份额代码字符串
     */
    @JsonIgnore
    private static final String REPLACE_FEN_E_STR = "*类份额(份额代码【***】)";

    /**
     * 将日期替换成模糊的格式 XXXX年XX月XX日
     *
     * @param input 文本
     * @return String
     * @author songqiang
     * @date 2024/6/27
     **/
    @JsonIgnore
    public static String replaceDate(String input){
        if (StrUtil.isBlankIfStr(input)) {
            return input;
        }
        Matcher matcherFenECode = FEN_E_CODE_REGEX.matcher(input);
        if (matcherFenECode.find()) {
            return REPLACE_FEN_E_STR;
        }
        Matcher matcher = DATE_REGEX.matcher(input);
        Map<String, String> replaceMap = new HashMap<>(2);
        while (matcher.find()) {
            String group = matcher.group();
            replaceMap.put(group, REPLACE_DATE_STR);
        }
        for (String s : replaceMap.keySet()) {
            input = input.replace(s, replaceMap.get(s));
        }
        return input;
    }

    public static Set<String> formatHeaderIndexSet(List<Cell> headers) {
        return headers.stream()
                .map(Table::formatCellIndex)
                .collect(Collectors.toSet());
    }

    public static String formatCellIndex(Cell cell) {
        return formatCellIndex(cell.getStartRow(), cell.getEndRow(), cell.getStartCol(), cell.getEndCol());
    }

    /**
     * 格式化单元格定位，顺序为：startRow,endRow,startCol,endCol
     *
     * @param index 单元格定位
     * @return 格式化的 startRow-endRow:startCol-endCol
     */
    public static String formatCellIndex(Integer... index) {
        if (index.length != 4) {
            throw new RuntimeException("格式化定位错误");
        }
        return String.format(LOCATION_FORMAT, index[0], index[1], index[2], index[3]);
    }

}
